//
// Copyright (c) 2011 Alex Nesterenko
//

#include "nane/RenderSystem/ogl/OGLRenderSystem.h"
#include "nane/RenderSystem/ogl/OGLWindowContext.h"
#include "nane/RenderSystem/ogl/OGLBufferManager.h"
#include "nane/core/log.h"
//#include "RenderSystem/ogl/Texture.h"

#define GET_A_FLOAT_FROM_ARGB32( argb ) ( ((float)(argb >> 24)) / 255.0f )
#define GET_R_FLOAT_FROM_ARGB32( argb ) ( ((float)((argb >> 16) & 0xFF)) / 255.0f )
#define GET_G_FLOAT_FROM_ARGB32( argb ) ( ((float)((argb >> 8) & 0xFF)) / 255.0f )
#define GET_B_FLOAT_FROM_ARGB32( argb ) ( (float)(argb & 0xFF) / 255.0f )
namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct OGLTexture
        : public ITexture
    {
        GLuint textureID;
        uint32 width;
        uint32 height;
        EPixelFormat format;
        byte* lock;

        OGLTexture( GLuint _textureID, uint32 _width, uint32 _height, EPixelFormat _format )
            : textureID( _textureID )
            , width( _width )
            , height( _height )
            , format( _format )
            , lock( NULL )
        {

        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OGLRenderSystem::OGLRenderSystem()
        : m_windowContext( NULL )
        , m_depthMask( false )
        , m_bufferManager( NULL )
        , m_activeTexture( 0 )
        , m_currentTexture( NULL )
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OGLRenderSystem::~OGLRenderSystem()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::initialize()
    {
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::shutdown()
    {
        if( m_windowContext != NULL )
        {
            m_windowContext->shutdown();
            releaseSubSystem( m_windowContext );
        }
        m_windowContext = NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::initializeContext( IWindow::OSHandle _windowHandle, uint32 _resWidth, uint32 _resHeight )
    {
        createSubSystem( &m_windowContext );
        if( m_windowContext == NULL )
        {
            LOG_ERROR("Failed to create OGL window context");
            return false;
        }
        if( m_windowContext->initialize(_windowHandle) == false )
        {
            LOG_ERROR("Failed to initialize OGL window context");
            return false;
        }
        m_depthMask = false;
        OGLAPI::glFrontFace(GL_CW);
        OGLAPI::glDepthMask(GL_FALSE);
        OGLAPI::glDisable(GL_DITHER);
        OGLAPI::glEnable(GL_TEXTURE_2D);
        m_bufferManager = new OGLBufferManager();
        OGLAPI::glViewport(0, 0, _resWidth, _resHeight);
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IVertexBuffer* OGLRenderSystem::createVertexBuffer( EResourceUsage::raw_type _usage, uint32 _verticesNum, uint32 _vertexSize
                                                      , VertexElement* _elements, uint32 _elementsNum, byte* _initData )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        return m_bufferManager->createVertexBuffer(enum_cast<EResourceUsage>(_usage), _verticesNum, _vertexSize, _elements, _elementsNum, _initData);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::releaseVertexBuffer( IVertexBuffer* _vertexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->releaseVertexBuffer(_vertexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    byte* OGLRenderSystem::lockVertexBuffer( IVertexBuffer* _vertexBuffer, uint32 _offset, uint32 _size )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        return m_bufferManager->lockVertexBuffer(_vertexBuffer, _offset, _size);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::unlockVertexBuffer( IVertexBuffer* _vertexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->unlockVertexBuffer(_vertexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setVertexBuffer( uint16 _streamNumber, IVertexBuffer* _vertexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->setVertexBuffer(_streamNumber, _vertexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IIndexBuffer* OGLRenderSystem::createIndexBuffer( EResourceUsage::raw_type _usage, uint32 _indiciesNum, uint16* _initData )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        return m_bufferManager->createIndexBuffer(enum_cast<EResourceUsage>(_usage), _indiciesNum, _initData);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::releaseIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        return m_bufferManager->releaseIndexBuffer(_indexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint16* OGLRenderSystem::lockIndexBuffer( IIndexBuffer* _indexBuffer, uint32 _offset, uint32 _size )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        return m_bufferManager->lockIndexBuffer(_indexBuffer, _offset, _size);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::unlockIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->unlockIndexBuffer(_indexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->setIndexBuffer(_indexBuffer);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ITexture* OGLRenderSystem::createTexture( EResourceUsage::raw_type _usage, uint32 _width, uint32 _height, EPixelFormat::raw_type _format )
    {
        GLuint textureID = 0;
        OGLTexture* oglTexture = NULL;
        OGLAPI::glGenTextures(1, &textureID);
        if( textureID != 0 )
        {
            setActiveTexture_(textureID);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
            OGLAPI::glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
            OGLAPI::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
            oglTexture = new OGLTexture(textureID, _width, _height, enum_cast<EPixelFormat>(_format));
        }
        return oglTexture;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::releaseTexture( ITexture* _texture )
    {
        OGLTexture* oglTexture = static_cast<OGLTexture*>(_texture);
        if( oglTexture != NULL )
        {
            if( m_activeTexture == oglTexture->textureID )
            {
                setActiveTexture_(0);
            }
            OGLAPI::glDeleteTextures(1, &oglTexture->textureID);
            delete oglTexture;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    byte* OGLRenderSystem::lockTexture( ITexture* _texture, uint32 _x, uint32 _y, uint32 _width, uint32 _height, uint32* _pitch )
    {
        OGLTexture* oglTexture = static_cast<OGLTexture*>(_texture);
        *_pitch = _width*4;
        oglTexture->lock = new byte[_width*_height*4];
        return oglTexture->lock;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::unlockTexture( ITexture* _texture )
    {
        OGLTexture* oglTexture = static_cast<OGLTexture*>(_texture);
        setActiveTexture_(oglTexture->textureID);
        OGLAPI::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, oglTexture->width, oglTexture->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, oglTexture->lock);
        delete[] oglTexture->lock;
        oglTexture->lock = NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setTexture( uint32 _stage, ITexture* _texture )
    {
        OGLAPI::glActiveTexture(GL_TEXTURE0 + _stage);
        m_currentTexture = _texture;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::setRenderTarget( IRenderTarget* _target )
    {
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::beginScene()
    {
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::endScene()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::drawPrimitive( EPrimitiveType::raw_type _type, uint32 _startVertex, uint32 _primitiveCount )
    {
        OGLTexture* oglCurrentTexture = static_cast<OGLTexture*>(m_currentTexture);
        GLuint currentTextureID = oglCurrentTexture != NULL ? oglCurrentTexture->textureID : 0;
        setActiveTexture_(currentTextureID);

        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->drawPrimitive(enum_cast<EPrimitiveType>(_type), _startVertex, _primitiveCount);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::drawIndexedPrimitive( EPrimitiveType::raw_type _type, uint32 _baseVertexIndex, uint32 _minIndex
                                     , uint32 _verticesNum, uint32 _startIndex, uint32 _primitiveCount )
    {
        OGLTexture* oglCurrentTexture = static_cast<OGLTexture*>(m_currentTexture);
        GLuint currentTextureID = oglCurrentTexture != NULL ? oglCurrentTexture->textureID : 0;
        setActiveTexture_(currentTextureID);

        NANE_ASSERT( m_bufferManager != NULL, "BufferManager must be valid" );
        m_bufferManager->drawIndexedPrimitive(enum_cast<EPrimitiveType>(_type), _baseVertexIndex, _minIndex, _verticesNum, _startIndex, _primitiveCount);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::clearRenderTarget( EFrameBufferType::raw_type _type, uint32 _color, float _depth, uint16 _stencil )
    {
        GLbitfield frameBufferFlags = 0;
        if( (_type & EFrameBufferType::Color) != 0 )
        {
            frameBufferFlags |= GL_COLOR_BUFFER_BIT;
            OGLAPI::glClearColor(GET_R_FLOAT_FROM_ARGB32( _color ),
                                 GET_G_FLOAT_FROM_ARGB32( _color ),
                                 GET_B_FLOAT_FROM_ARGB32( _color ),
                                 GET_A_FLOAT_FROM_ARGB32( _color ));
        }
        if( (_type & EFrameBufferType::Depth) != 0 )
        {
            frameBufferFlags |= GL_DEPTH_BUFFER_BIT;
            if( m_depthMask == false )
            {
                OGLAPI::glDepthMask(GL_TRUE);
            }
            OGLAPI::glClearDepth(_depth);
        }
        if( (_type & EFrameBufferType::Stencil) != 0 )
        {
            frameBufferFlags |= GL_STENCIL_BUFFER_BIT;
            OGLAPI::glClearStencil(_stencil);
        }

        OGLAPI::glClear(frameBufferFlags);

        if( m_depthMask == false )
        {
            OGLAPI::glDepthMask(GL_FALSE);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::swapBuffers()
    {
        m_windowContext->swapBuffers();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setProjectionMatrix( const float* _projection )
    {
        OGLAPI::glMatrixMode(GL_PROJECTION);
        OGLAPI::glLoadMatrixf(_projection);
        OGLAPI::glMatrixMode(GL_MODELVIEW);
    }
     ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setModelViewMatrix( const float* _modelview )
    {
        OGLAPI::glLoadMatrixf(_modelview);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setLightingEnable( bool _enable )
    {
        if( _enable == true )
        {
            OGLAPI::glEnable(GL_LIGHTING);
        }
        else
        {
            OGLAPI::glDisable(GL_LIGHTING);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setCullMode( ECullMode::raw_type _mode )
    {
        bool enable = true;
        GLenum face = GL_BACK;
        if( enum_cast<ECullMode>(_mode) == ECullMode::Clockwise )
        {
            face = GL_FRONT;
        }
        else if( enum_cast<ECullMode>(_mode) == ECullMode::None )
        {
            enable = false;
        }
        OGLAPI::glCullFace(face);
        if( enable == true )
        {
            OGLAPI::glEnable(GL_CULL_FACE);
        }
        else
        {
            OGLAPI::glDisable(GL_CULL_FACE);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IVertexShader* OGLRenderSystem::createVertexShader( const byte* _buffer, uint32 _size )
    {
        return NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::setVertexShader( IVertexShader* _vertexShader )
    {
        return false;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::releaseVertexShader( IVertexShader* _vertexShader )
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IPixelShader* OGLRenderSystem::createPixelShader( const byte* _buffer, uint32 _size )
    {
        return NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OGLRenderSystem::setPixelShader( IPixelShader* _pixelShader )
    {
        return false;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::releasePixelShader( IPixelShader* _pixelShader )
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLRenderSystem::setActiveTexture_( GLuint _textureID )
    {
        if( m_activeTexture != _textureID )
        {
            OGLAPI::glBindTexture(GL_TEXTURE_2D, _textureID);
            m_activeTexture = _textureID;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
